use log::debug;
use smithay_client_toolkit::{
    compositor::SurfaceData,
    delegate_pointer,
    reexports::client::{
        Connection, Dispatch, Proxy as _, QueueHandle,
        protocol::{
            wl_pointer::{AxisRelativeDirection, WlPointer},
            wl_surface::{self, WlSurface},
        },
    },
    seat::pointer::{AxisScroll, PointerEvent, PointerEventKind, PointerHandler},
};

use crate::linux::{
    application_state::ApplicationState,
    events::{
        MouseButton,
        MouseDownEvent,
        MouseEnteredEvent,
        MouseExitedEvent,
        MouseMovedEvent,
        MouseUpEvent,
        ScrollData,
        ScrollWheelEvent,
        Timestamp,
        //
    },
    geometry::LogicalPixels,
};

impl From<AxisScroll> for ScrollData {
    fn from(value: AxisScroll) -> Self {
        let wheel_value120 = if value.value120 != 0 {
            value.value120
        } else {
            value.discrete * 120
        };
        Self {
            delta: LogicalPixels(value.absolute),
            wheel_value120,
            is_inverted: value.relative_direction == Some(AxisRelativeDirection::Inverted),
            is_stop: value.stop,
        }
    }
}

impl PointerHandler for ApplicationState {
    fn pointer_frame(&mut self, conn: &Connection, qh: &QueueHandle<Self>, _pointer: &WlPointer, events: &[PointerEvent]) {
        for event in events {
            let Some(window) = self.windows.get_mut(&event.surface.id()) else {
                continue;
            };
            let scale = window.current_scale;
            let window_id = window.window_id;

            _ = match event.kind {
                PointerEventKind::Enter { .. } => {
                    window.num_pointer_buttons_down = 0;
                    window.set_cursor = true;
                    let res = self.send_event(MouseEnteredEvent {
                        window_id,
                        location_in_window: event.position.into(),
                    });
                    if let Some(themed_pointer) = self.themed_pointer.take() {
                        let pointer_surface = themed_pointer.surface();
                        if let Some(pointer_surface_data) = pointer_surface.data() {
                            #[allow(clippy::cast_possible_truncation)]
                            let pointer_surface_event = wl_surface::Event::PreferredBufferScale {
                                factor: scale.round() as i32,
                            };
                            debug!("Setting cursor scale to {scale:?}");
                            Dispatch::<WlSurface, SurfaceData>::event(
                                self,
                                pointer_surface,
                                pointer_surface_event,
                                pointer_surface_data,
                                conn,
                                qh,
                            );
                        }
                        self.themed_pointer = Some(themed_pointer);
                    }
                    res
                }
                PointerEventKind::Leave { .. } => {
                    window.num_pointer_buttons_down = 0;
                    self.send_event(MouseExitedEvent {
                        window_id,
                        location_in_window: event.position.into(),
                    })
                }
                PointerEventKind::Motion { time } => self.send_event(MouseMovedEvent {
                    window_id,
                    location_in_window: event.position.into(),
                    timestamp: Timestamp(time),
                }),
                PointerEventKind::Press { button, serial, time } => {
                    if window.num_pointer_buttons_down == 0 {
                        self.last_pointer_down_event_serial = Some(serial);
                    }
                    window.num_pointer_buttons_down += 1;
                    self.send_event(MouseDownEvent {
                        window_id,
                        button: MouseButton(button),
                        location_in_window: event.position.into(),
                        timestamp: Timestamp(time),
                    })
                }
                PointerEventKind::Release { button, serial: _, time } => {
                    // Sometimes the Release event can occur without the Press event beforehand. E.g., when dismissing the window menu.
                    if window.num_pointer_buttons_down > 0 {
                        window.num_pointer_buttons_down -= 1;
                    }
                    self.send_event(MouseUpEvent {
                        window_id,
                        button: MouseButton(button),
                        location_in_window: event.position.into(),
                        timestamp: Timestamp(time),
                    })
                }
                PointerEventKind::Axis {
                    time,
                    horizontal,
                    vertical,
                    ..
                } => {
                    debug!("wl_pointer vertical={vertical:?}");
                    self.send_event(ScrollWheelEvent {
                        window_id,
                        location_in_window: event.position.into(),
                        timestamp: Timestamp(time),
                        horizontal_scroll: horizontal.into(),
                        vertical_scroll: vertical.into(),
                    })
                }
            };
        }
    }
}

delegate_pointer!(ApplicationState);
